#include "../../dfruntime.h"
#include "../../scheduling.h"
#include "../../misc.h"
#include <stddef.h>

#define fib_threshold 5

// Needed
// -> Function.
// -> Frames
//
//
//

#define PACKED __attribute__((__packed__))
#define INFO_BODY \
  void (*fn)();  \
  long sc_initial; \
  long numret; \
  long recursive; \
  size_t framesize; \
  struct misc_info *misc 




int fib_orig( int n ) {
  
  return n <= 1 ?
      1  : fib_orig(n-1) + fib_orig(n-2) ;

}
// Frame for entry function
struct fib_df_entry_frame_struct {
  struct dfrt_infos * infos;
  int sc;
  // This is the output : waiter frame only.
  struct fib_df_waiter_frame_struct * ret1;
  // Parameters :
  int n;
} PACKED;

struct fib_df_waiter_frame_struct {
  struct dfrt_infos * infos;
  int sc;
  struct fib_df_adder_frame_struct * ret1;
  int retval;
} PACKED;

struct fib_df_adder_frame_struct {
  struct dfrt_infos * infos;
  int sc;
  struct fib_df_waiter_frame_struct * ret1;
  int a;
  int b;
} PACKED;

struct fib_df_writer_frame_struct {
  struct dfrt_infos * infos;
  int sc;
  struct fib_df_waiter_frame_struct * ret1;
  int res;
} PACKED;



void fib_df_waiter_a () {
  struct fib_df_waiter_frame_struct *fp = dfrt_get_cfp();
  int retval = fp->retval;
  DEBUG( "WAITER_A : %d", retval );

  fp->ret1->a = retval; 
  dfrt_delayed_tdecrease( fp->ret1 );
  dfrt_tend();
}

struct misc_info fib_df_waiter_a_miscinfo = { "fib_df_waiter_a", "A waiter for a parameter of add"};

struct wrdesc_waiter_a {
  int count;
  struct wrvec wrvec1;
  bool mustfree;
} PACKED ;

struct wrdesc_waiter_a wrdesc_waiter_a =
   { 1, // Count
   // Wrvec
   { offsetof(struct fib_df_adder_frame_struct, a), sizeof(int) },
   false
   };


struct dfrt_info_waiter_a {
  INFO_BODY;
  struct wrdesc_waiter_a *wrdesc;
} PACKED;


struct dfrt_info_waiter_a fib_df_waiter_a_infos = {
  // Pointer to the code:
  &fib_df_waiter_a,
  2, // One for entry, one for retval.
  1, // only one ret place
  0, // nonrecursive
  sizeof(struct fib_df_waiter_frame_struct)
};


__attribute__((constructor))
void dfrt_waiter_a_completor() {
   fib_df_waiter_a_infos.misc = &fib_df_waiter_a_miscinfo;
   fib_df_waiter_a_infos.wrdesc = &wrdesc_waiter_a;
}

// Rest is to be completed ( wr structs ... )


void fib_df_waiter_b () {
  struct fib_df_waiter_frame_struct *fp = dfrt_get_cfp();
  int retval = fp->retval;
  DEBUG( "WAITER_B : %d", retval );

  fp->ret1->b = retval; 
  dfrt_delayed_tdecrease( fp->ret1 );
  dfrt_tend();
}

struct misc_info fib_df_waiter_b_miscinfo = { "fib_df_waiter_b", "A waiter for b parameter of add"};

// Wrdesc for waiter b:
struct wrdesc_waiter_b {
  int count;
  struct wrvec wrvec1;
  bool mustfree;
} PACKED;

struct wrdesc_waiter_b wrdesc_waiter_b = 
  { 1, // Count of writes in this output frame
  // Wrvec
   { offsetof(struct fib_df_adder_frame_struct, b), sizeof(int) },
  false
   };


struct dfrt_info_waiter_b {
  INFO_BODY;
  struct wrdesc_waiter_b *wrdesc;
} PACKED;


struct dfrt_info_waiter_b fib_df_waiter_b_infos = {
  // Pointer to the code:
  &fib_df_waiter_b,
  2, // One for entry, one for retval.
  1, // only one ret place
  0, // nonrecursive
  sizeof(struct fib_df_waiter_frame_struct),
};


__attribute__((constructor))
void dfrt_waiter_b_completor() {
   fib_df_waiter_b_infos.misc = &fib_df_waiter_b_miscinfo;
   fib_df_waiter_b_infos.wrdesc = &wrdesc_waiter_b;
}


void fib_df_adder () {
  struct fib_df_adder_frame_struct * fp = dfrt_get_cfp();
  fp->ret1->retval= fp->a + fp->b;
  DEBUG( "ADDER : %d + %d = %d",fp->a, fp->b, fp->ret1->retval );
  dfrt_delayed_tdecrease( fp->ret1 );
  dfrt_tend();
}


struct misc_info fib_df_adder_miscinfo = { "fib_df_adder", "An adder to merge two results."};

struct wrdesc_adder {
  int count;
  // Only writes once in this frame.
  struct wrvec wrvec1;
  bool mustfree;
} PACKED;

struct wrdesc_adder wrdesc_adder = {
  1,
  { offsetof( struct fib_df_waiter_frame_struct, retval ), sizeof(int) },
  false
};


struct dfrt_info_adder {
  INFO_BODY;
  struct wrdesc_adder *wrdesc_adder;
} PACKED ;

struct dfrt_info_adder fib_df_adder_infos = { 
  // Pointer to the code:
  &fib_df_adder,
  3, // Two for entry, one for retval ptr.
  1, // only one ret place
  0, // nonrecursive
  sizeof(struct fib_df_adder_frame_struct)
};

__attribute__((constructor))
void dfrt_adder_completor() {
   fib_df_adder_infos.misc = &fib_df_adder_miscinfo;
   fib_df_adder_infos.wrdesc_adder = &wrdesc_adder;
}

void fib_df_writer () {
  struct fib_df_writer_frame_struct * fp = dfrt_get_cfp();
  fp->ret1->retval = fp->res;
  dfrt_delayed_tdecrease( fp->ret1 );
  dfrt_tend();
}


struct misc_info fib_df_writer_miscinfo = { "fib_df_writer", "A writer for one result."};

struct wrdesc_writer {
  int count;
  // Only writes once in this frame.
  struct wrvec wrvec1;
  bool mustfree;
} PACKED;

struct wrdesc_writer wrdesc_writer = {
  1,
  { offsetof( struct fib_df_waiter_frame_struct, retval ), sizeof(int) },
  false
};


struct dfrt_info_writer {
  INFO_BODY;
  struct wrdesc_writer *wrdesc_writer;
} PACKED ;

struct dfrt_info_writer fib_df_writer_infos = {
  // Pointer to the code:
  &fib_df_writer,
  1, // One for entry only.
  1, // only one ret place
  0, // nonrecursive
  sizeof(struct fib_df_writer_frame_struct)
};

__attribute__((constructor))
void dfrt_writer_completor() {
   fib_df_writer_infos.misc = &fib_df_writer_miscinfo;
   fib_df_writer_infos.wrdesc_writer = &wrdesc_writer;
}




/******************** Structures for entry ***************/

extern void fib_df_entry();
struct misc_info fib_df_entry_miscinfo = { "fib_df_entry", "The fib entry function."};

struct wrdesc_entry {
  int count;
  struct wrvec wrvec1;
  bool mustfree;
} PACKED;

struct wrdesc_entry wrdesc_entry = {
  1,
  { offsetof( struct fib_df_waiter_frame_struct, retval ), sizeof(int) },
  false
};


struct dfrt_info_entry {
  INFO_BODY;
  struct wrdesc_entry *wrdesc_entry;
} PACKED;

struct dfrt_info_entry fib_df_entry_infos = {
  // Pointer to the code:
  &fib_df_entry,
  1, // One for creator
  0, // no ret places
  1, // recursive
  sizeof(struct fib_df_entry_frame_struct),
};

__attribute__((constructor))
void dfrt_entry_completor() {
   fib_df_entry_infos.misc = &fib_df_entry_miscinfo;
   fib_df_entry_infos.wrdesc_entry = &wrdesc_entry;
}


void fib_df_entry() {
  // Getting arguments
  struct fib_df_entry_frame_struct * fp = 
    dfrt_get_cfp();
  // Get the parameters back :
  int n = fp->n;


  // Result 
  int res = 1;
  // A call for optimization.
  if ( n < fib_threshold ) {
    res = fib_orig(n);
    struct fib_df_writer_frame_struct *writer =
            dfrt_tcreate(1,sizeof(struct fib_df_writer_frame_struct));
    writer->infos = &fib_df_writer_infos;
    writer->res = res;
    writer->ret1 = fp->ret1;


    // and tdec
    dfrt_delayed_tdecrease( writer );
    dfrt_tend();
    return;
  } 
  
  if ( n <= 1 ) {
    res = 1;
    struct fib_df_writer_frame_struct *writer =
            dfrt_tcreate(1,sizeof(struct fib_df_writer_frame_struct));
    writer->infos = &fib_df_writer_infos;
    writer->res = res;
    writer->ret1 = fp->ret1;


    // and tdec
    dfrt_delayed_tdecrease( writer );
    dfrt_tend();
    return;
  }


  // We need to build the standard execution with :
  //  - A call to fib entry with n-1, ret to waiter_a 
  //  - Another with n-2 ret to waiter_b
  //  - A call to add ret to self_ret
  //  - A call to waiter_a
  //  - A call to waiter_b
  
  struct fib_df_entry_frame_struct * fib_1
    = dfrt_tcreate(1, sizeof(struct fib_df_entry_frame_struct));



  struct fib_df_entry_frame_struct * fib_2
    = dfrt_tcreate(1, sizeof(struct fib_df_entry_frame_struct));

  struct fib_df_waiter_frame_struct * waiter_a
    = dfrt_tcreate(2, sizeof(struct fib_df_waiter_frame_struct));

  struct fib_df_waiter_frame_struct * waiter_b
    = dfrt_tcreate(2, sizeof(struct fib_df_waiter_frame_struct));

  struct fib_df_adder_frame_struct * adder 
    = dfrt_tcreate(3, sizeof(struct fib_df_adder_frame_struct));

  // Filling fib1 fields.
  fib_1->infos = &fib_df_entry_infos;
  fib_1->n = n-1;
  fib_1->ret1 = waiter_a;
  
  // fib1 is ready.
  dfrt_delayed_tdecrease( fib_1 );

  // Filling fib2 fields.
  fib_2->infos = &fib_df_entry_infos;
  fib_2->n = n-2;
  fib_2->ret1 = waiter_b;

  // fib2 is ready :
  dfrt_delayed_tdecrease( fib_2 );

  //  Filling waiter a fields.
  waiter_a->infos = &fib_df_waiter_a_infos;
  waiter_a->ret1 = adder;

  // waiter_a is allowed to be triggered :
  dfrt_delayed_tdecrease( waiter_a );

  // Filling waiter b fields
  waiter_b->infos = &fib_df_waiter_b_infos;
  waiter_b->ret1 = adder;

  // waiter_b is allowed to be triggered :
  dfrt_delayed_tdecrease( waiter_b );
  
  // Filling adder fields.
  adder->infos = &fib_df_adder_infos;
  adder->ret1 = fp->ret1; // Adder finishes the call.
  
  // Adder can be decreased, still waiting for 2 more args :
  dfrt_delayed_tdecrease( adder );

  // And we should be finished : )
  dfrt_tend();
}




struct misc_info printwaiter_miscinf = {"Printwaiter", "Printwaiter Infos" };


void printwait () {
  struct fib_df_waiter_frame_struct * fp = dfrt_get_cfp();
  DEBUG( "Result :: %d", fp->retval);
  printf( "Result :: %d\n", fp->retval);
  fflush(stderr);
  dfrt_tend();
}

struct dfrt_info_waiter_a printwaiter_inf = {
  // Pointer to the code:
  &printwait,
  2, 
  0, // only one ret place
  0, // nonrecursive
  &printwaiter_miscinf
};



static int __n = 16;

void start() {
   // Prepare a call for fib :
   struct fib_df_waiter_frame_struct *waiter =
     dfrt_tcreate( 2, sizeof( struct fib_df_waiter_frame_struct ));
   struct fib_df_entry_frame_struct * entry = 
     dfrt_tcreate( 1, sizeof( struct fib_df_entry_frame_struct ));

   // Fill in the structs :
   entry->infos = &fib_df_entry_infos;
   entry->n = __n;
   entry->ret1 = waiter;

   dfrt_delayed_tdecrease( entry );

   waiter->infos = &printwaiter_inf;

   dfrt_delayed_tdecrease( waiter );
   dfrt_tend();
}


int main(int argc, char ** argv) {
  if ( argc > 1 ) {
      __n = atoi( argv[1] );
  }
  int nproc = 2;
  int ncore = 2;


  if ( argc > 2  ) {
      nproc = atoi( argv[2] );
  }
  if ( argc > 3 ) {
      ncore = atoi( argv[3] );
  }

  dfrt_init( nproc, ncore, &start );
}
